# -*- mode: python; coding: utf-8; -*-
##---------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##---------------------------------------------------------------------------##
"""
Canvas widgets. Various basic widgets from text, to image, to vector, to frames.
"""
from . import constants as vcnst
from ..utillib.misc import Point, Block, Size
from ..settings import CONF_OPT
from .toolkit import Canvas


class _CanvasItem(object):
    """
    Canvas item wrapper.

    Allow items accessible only by tag/item to be used as seperate objects.
    """
    _canvas = None
    _index = 0

    def __getitem__(self, key):
        """
        Key access to canvas to get item option.

        .. CanvasItem[key] -> *
        """
        return self._canvas.itemcget(self, key)

    def __setitem__(self, key, value):
        """
        Key access to canvas to set item option.

        .. CanvasItem[key] = value
        """
        self.config(**{key: value})

    def config(self, **kwargs):
        """
        Config canvas item.

        .. config([*item keywords*]) -> None
        """
        self._canvas.itemconfig(self, **kwargs)

    def addtag(self, item):
        """
        Append tag group for canvas item.

        Call config method to update item.

        .. addtag(item:str) -> None
        """
        tags = [self.__class__.__name__]
        if item is not None:
            tags.append(item)
        self.config(tags=tuple(tags))

    def deltag(self, item):
        """
        Remove tag group for canvas item.

        .. deltag(items:str) -> None
        """
        self._canvas.dtag(self, item)

    @property
    def coord(self):
        """
        Return canvas item coords.

        .. coord -> Point(int, int)
        """
        if len(self._canvas.coords(self)) == 0:
            return Point(0, 0)
        return Point(*self._canvas.coords(self))

    @property
    def bbox(self):
        """
        Return canvas item coords.

        .. bbox -> Block(int, int)
        """
        return Block(*self._canvas.bbox(self))

    def __str__(self):
        """
        Return canvas item tag/item.

        .. -> str
        """
        return str(self._index)

    def move_to(self, xcoord, ycoord):
        """
        Do absolute canvas item move.

        Call coords method with absolute position.

        .. move_to(xcoord:int, ycoord:item) -> None
        """
        self._canvas.coords(self, (xcoord, ycoord,))

    def move(self, xdelta, ydelta):
        """
        Do relative canvas item move.

        Call move method with relative position from coord.

        .. move_to(xcoord:int, ycoord:item) -> None
        """
        self._canvas.move(self, xdelta, ydelta)

    def raise_item(self):
        """
        Do raise canvas item above all other.

        Call tag raise method to position on top.

        .. raise_item() -> None
        """
        self._canvas.tag_raise(self)

    def lower_item(self):
        """
        Do lower canvas item below all other.

        Call tag lower method to position on bottom.

        .. lower_item() -> None
        """
        self._canvas.tag_lower(self)

    def show(self):
        """
        Show canvas item.

        Config canvas item state normal.

        .. show() -> None
        """
        self._canvas.show(self)

    def hide(self):
        """
        Hide canvas item.

        Config canvas item state hidden.

        .. hide() -> None
        """
        self._canvas.hide(self)

    def disable(self):
        """
        Disable canvas item.

        Config canvas item state disable.

        .. disable() -> None
        """
        self._canvas.disable(self)

    def delete(self):
        """
        Delete canvas item.

        .. delete() -> None
        """
        self._canvas.delete(self)

    def bind(self, event, callback, add=None):
        """
        Bind canvas item with event.

        Bind canvas item with event to call callback method.

        .. bind(event:str, callback:func[, add]) -> None
        """
        self._canvas.tag_bind(self, event, callback, add)


class GroupItem(_CanvasItem):
    """
    Tag group for Canvas... used for grouping items (eg. pile cards).
    """

    def __init__(self, canvas):
        """
        Init canvas tag group.

        Set tag/group and call canvas delete tag method to reset.

        .. \__init__(canvas:Canvas) -> None
        """
        super(GroupItem, self).__init__()
        self._index = 'Group%d' % id(self)
        self._canvas = canvas
        self._canvas.dtag(self)

    def addtag(self, items):
        """
        Append tag group for canvas item.

        Overide to call canvas add tag with tag method to entire tag/group.

        .. addtag(items:str|seq(str ...)) -> None
        """
        self._canvas.addtag_withtag(items, self)


class ImageItem(_CanvasItem):
    """
    Image for Canvas... used for card and base image items.
    """

    def __init__(self, canvas, coord, group=None, **options):
        """
        Init canvas image.

        Set init coords, then call canvas method to create image on canvas.
        Final add tag for group when given.

        .. \__init__(canvas:Canvas, coord:Point(int, int), group=None[,
          *Canvas.ImageItem keywords*]) -> None
        """
        super(ImageItem, self).__init__()
        self.init_coord = coord = Point(*coord)
        self._canvas = canvas
        options.setdefault('anchor', vcnst.ANCHOR_NW)
        self._index = canvas.create_image(coord, options)
        self.addtag(group)


class LineItem(_CanvasItem):
    """
    Line for Canvas... used for drawing a line to indicate pile build/play.
    """

    def __init__(self, canvas, coord, group=None, **options):
        """
        Init canvas line.

        Call canvas create line method to create line. Final add tag for group
        when given.

        .. \__init__(canvas:Canvas, coord:Point(int, int), group=None[,
          *Canvas.ImageItem keywords*]) -> None
        """
        super(LineItem, self).__init__()
        self._canvas = canvas
        options.setdefault('width', 7)
        options.setdefault('fill', CONF_OPT['colors']['hintarrow'])
        self._index = canvas.create_line(coord, options)
        self.addtag(group)


class RectangleItem(_CanvasItem):
    """
    Rectangle for Canvas... used for drawing a rectangle to outline a region.
    """

    def __init__(self, canvas, coord, group=None, **options):
        """
        Init canvas rectangle.

        Prepare given coord when coord is not already a Block tuple. When given
        coord len is 2, check for Point and/or Size tuple and create the Block.
        Call canvas create rectangle method to create rectangle. Final add tag
        for group when given.

        .. \__init__(canvas:Canvas, coord:Point(int, int), group=None[,
          *Canvas.ImageItem keywords*]) -> None
        """
        super(RectangleItem, self).__init__()
        if len(coord) == 2 and isinstance(coord[0], Point):
            if isinstance(coord[1], Point):
                coord = Block._from_points(*coord)
            elif isinstance(coord[1], Size):
                coord = Block._from_point_size(*coord)
        if len(coord) == 4:
            coord = Block(*coord)
        assert isinstance(coord, Block)
        self._canvas = canvas
        self._index = canvas.create_rectangle(coord, options)
        self.addtag(group)


class TextItem(_CanvasItem):
    """
    Text for Canvas... used for pile annotations (eg num card/round or base).
    """

    def __init__(self, canvas, coord, group=None, **options):
        """
        Init canvas rectangle.

        Set coord. Call canvas create text method to create rectangle. Final add
        tag for group when given.

        .. \__init__(canvas:Canvas, coord:Point(int, int), group=None[,
          *Canvas.ImageItem keywords*]) -> None
        """
        super(TextItem, self).__init__()
        self.init_coord = coord = Point(*coord)
        if canvas.preview:
            return
        self._canvas = canvas
        if 'fontname' in options:
            options['font'] = TextItem._font_by_name(options['fontname'])
            del options['fontname']
        options.setdefault('anchor', vcnst.ANCHOR_NW)
        options.setdefault('fill', canvas._text_color)
        self._index = canvas.create_text(coord, options)
        self.addtag(group)
        self.text_format = None

    @staticmethod
    def _font_by_name(name):
        import tkinter.font
        from ..settings import CONF_OPT
        font = CONF_OPT['fonts'][name]
        family, size = font[:2]
        kwargs = {'family': family, 'size': int(size)}

        for value in font[2:]:
            if value in ['bold', 'normal']:
                kwargs['weight'] = value
            elif value in ['italic', 'roman']:
                kwargs['slant'] = value
        return tkinter.font.Font(**kwargs)

    @staticmethod
    def get_text_width(text, fontname='canvas_default', root=None):
        import tkinter.font
        font = TextItem._font_by_name(fontname)
        return tkinter.font.Font(root=root, font=font).measure(text)


class WindowItem(_CanvasItem):
    """
    Create a window for a toolkit widget on the canvas.
    """

    def __init__(self, canvas, coord, group=None, **options):
        """
        Create a tooltip for a **stack** binding button press to delete.
        """
        self._canvas = canvas
        options.setdefault('anchor', vcnst.ANCHOR_N)
        self._index = canvas.create_window(coord, options)
        self.addtag(group)